﻿using System;
using SoftwareConsulting.BI11.NumericalMethods.Curves;
using SoftwareConsulting.BI11.NumericalMethods.Statistics;
using SoftwareConsulting.BI11.Studio.InternalServices;

namespace SoftwareConsulting.BI11.NumericalMethods.Statistics
{
    /// <summary>
    /// Класс треугольного распределения
    /// </summary>
    public sealed class TriangularDistribution : ProbabilityDensityFunction
    {
        private double _lowLimit;//нижняя граница
        private double _highLimit;//верхняя граница	    
        private double _peakLocation;//положение вершины

        /// <summary>
        /// Конструктор класса
        /// </summary>
        /// <param name="lowLimit">нижняя граница</param>
        /// <param name="highLimit">верхняя граница</param>
        /// <param name="peakLocation">положение вершины</param>
        public TriangularDistribution(double lowLimit, double highLimit, double peakLocation)
        {
            if (lowLimit >= highLimit)
                throw new ArgumentOutOfRangeException("lowLimit");
            if (peakLocation < lowLimit || peakLocation > highLimit)
                throw new ArgumentOutOfRangeException("peakLocation");
            _lowLimit = lowLimit;
            _highLimit = highLimit;
            _peakLocation = peakLocation;
        }

        /// <summary>
        /// Конструктор класса
        /// </summary>
        /// <param name="histogram">Гистограмма, из которой вытягиваются параметры по наилучшим предположениям</param>
        public TriangularDistribution(Histogram histogram)
        {
            _highLimit = histogram.StandardDeviation * 1.73205080756888;
            _peakLocation = histogram.Average;
            _lowLimit = _peakLocation - _highLimit;
            _highLimit += _peakLocation;
        }

        /// <summary>
        /// Возвращает среднее распределения
        /// </summary>
        public override double Average
        {
            get 
            { 
                return (_lowLimit + _highLimit + _peakLocation) / 3; 
            }
            set
            {
                Assert.Fail(Assert.Id.Assert00292);//не поддерживается для данного рапределения
            }
        }

        /// <summary>
        /// Возвращает значение функции плотности распределения
        /// </summary>
        /// <param name="x"></param>
        /// <returns>вероятность, нахождения случайной переменной меньше или равной, чем X</returns>
        public override double GetDistributionValue(double x)
        {
            if (x < _lowLimit)
                return 0;
            if (x < _peakLocation)
                return (x - _lowLimit) * (x - _lowLimit) / ((_highLimit - _lowLimit) * (_peakLocation - _lowLimit));
            if (x < _highLimit)
                return 1 - (_highLimit - x) * (_highLimit - x) / ((_highLimit - _lowLimit) * (_highLimit - _peakLocation));
            return 1;
        }

        /// <summary>
        /// Возвращает название распределения
        /// </summary>
        public override string Name
        {
            get 
            { 
                return "Triangular distribution"; 
            }
        }

        /// <summary>
        /// Возвращает/устанавливает параметры распределения
        /// </summary>
        public override double[] Parameters
        {
            get 
            { 
                return new double[] { _lowLimit, _highLimit, _peakLocation }; 
            }
            set
            {
                _lowLimit = value[0];
                _highLimit = value[1];
                _peakLocation = value[2];
            }
        }

        /// <summary>
        /// Возвращает значение, для которого функция распределения равна указанному значению X
        /// </summary>
        /// <param name="x"></param>
        /// <returns></returns>
        protected override double GetInversedValue(double x)
        {
            return (x < (_peakLocation - _lowLimit) / (_highLimit - _lowLimit))
                            ? Math.Sqrt(x * (_highLimit - _lowLimit) * (_peakLocation - _lowLimit)) + _lowLimit
                            : _highLimit - Math.Sqrt((1 - x) * (_highLimit - _lowLimit) * (_highLimit - _peakLocation));
        }

        /// <summary>
        /// Вычисляет значение, основываясь на функции плотности
        /// </summary>
        /// <param name="x"></param>
        /// <returns></returns>
        public override double GetValue(double x)
        {
            if (x < _lowLimit)
                return 0;
            if (x < _peakLocation)
                return 2 * (x - _lowLimit) / ((_highLimit - _lowLimit) * (_peakLocation - _lowLimit));
            if (x < _highLimit)
                return 2 * (_highLimit - x) / ((_highLimit - _lowLimit) * (_highLimit - _peakLocation));
            return 0;
        }

        /// <summary>
        /// Возвращает дисперсию распределения
        /// </summary>
        public override double Variance
        {
            get 
            { 
                return (_lowLimit * _lowLimit + _highLimit * _highLimit + _peakLocation * _peakLocation - _lowLimit * _highLimit - _lowLimit * _peakLocation - _highLimit * _peakLocation) / 18; 
            }
        }
    }
}
